/**
 * @method File 文件处理解决方案
 * @author Lkx
 * @for Kxui
 * @for method
 *
 * @method analysis 文件解析
 * @method dataURLtoBlob dataURL 转 blob
 * @method fileOrBlobToDataURL file/blob 转 dataURL
 * @method dataURLtoImage dataURL 转 image
 * @method imagetoCanvas image 转 canvas
 * @method canvasResizetoFile canvas 转 file/blob
 * @method compressAccurately 图片压缩
 */

(function (factory) {

  if ((typeof exports === 'object') && (typeof module !== 'undefined')) {
    module.exports = factory();
  } else {
    window.Kxui.file = factory();
  }

}(function () {

  'use strict';

  let Kxui = window.Kxui;

  /**
   * 逻辑层
   * @method Logic
   * @for File
   * @param {object} parameter 配置参数
   */
  let Logic = function (parameter) {
    this.parameter = (typeof parameter === 'object') ? parameter : {};
    this.init();
  };

  Logic.prototype = {

    /**
     * 初始化
     * @method init
     * @for Logic
     */
    init: function () {
      let el = Kxui.method.getDom(this.parameter.el);
      if (!this.parameter.el) {
        throws(0, 0);
      } else if (!el) {
        throws(1, 1, this.parameter.el);
      } else if (el.length) {
        for (let i = 0; i < el.length; i++) {
          this.dataName(el[i]);
        }
      } else {
        this.dataName(el);
      }
    },

    /**
     * 标签拦截
     * @method dataName
     * @for init
     * @param {object} el 标签节点
     */
    dataName(el) {
      let tagName = el.tagName;
      let type = Kxui.method.atrDom(el, 'type');
      if (tagName === 'INPUT' && type === 'file') {
        this.variable(el);
      } else {
        throws(0, 2, this.parameter.el);
      }
    },

    /**
     * 变量生成
     * @method variable
     * @for dataName
     * @param {object} el 标签节点
     */
    variable: function (el) {
      let I = +this.parameter.index || 0;
      this.index = (this.index || this.index === 0 ? this.index + 1 : 0);
      this.imageConfig = (typeof this.parameter.image === 'object') ? this.parameter.image : {};
      this.callback = (typeof this.parameter.callback === 'function') ? this.parameter.callback : function () {};
      this.event(el, (this.index + I));
    },

    /**
     * 事件绑定
     * @method event
     * @for variable
     * @param {object} el 标签节点
     * @param {number} index 多标签节点下标
     */
    event: function (el, index) {
      let that = this;

      // 监听上传文件动态
      el.addEventListener('change', onChange);

      /**
       * 监听上传文件动态
       * @method onChange
       * @for event
       */
      function onChange(e) {
        let E = e || window.event;
        let file = E.target.files;
        let fileGroup = [];
        for (let i = 0; i < file.length; i++) {
          let reader = new window.FileReader();

          reader.onload = function (event) {
            let callbackGroup = {};
            let formData = new FormData();
            let dataURL = event.target.result;
            let blob = dataURLtoBlob(dataURL);

            if (blob.type.indexOf('image') >= 0) {
              compressAccurately(dataURL, that.imageConfig, function (blob) {

                formData.append('file', blob);

                callbackGroup.blob = blob;
                callbackGroup.formData = formData;

                fileOrBlobToDataURL(blob, function (image) {
                  callbackGroup.image = image;
                  fileGroup.push(callbackGroup);

                  if (i === file.length - 1) {
                    that.callback(fileGroup, index);
                  }
                });
              });
            } else {
              formData.append('file', blob);

              callbackGroup.blob = blob;
              callbackGroup.formData = formData;

              fileGroup.push(callbackGroup);

              if (i === file.length - 1) {
                that.callback(fileGroup, index);
              }
            }
          };

          reader.readAsDataURL(file[i]);
        }
      }
    }
  };

  /**
   * 支持的图片压缩格式
   * @method checkImageType
   * @for compressAccurately
   * @param {string} type 图片格式
   */
  function checkImageType(type) {
    let image = ['image/png', 'image/jpeg'];
    for (let i = 0; i < image.length; i++) {
      if (image[i].indexOf(type) >= 0) {
        return image[i];
      } else if (i === image.length - 1) {
        return false;
      }
    }
  }

  /**
   * dataURL 转 blob
   * @method dataURLtoBlob
   * @for File/Logic/compressAccurately
   * @param {object} dataURL 需要转换的内容
   */
  function dataURLtoBlob(dataURL) {
    let BASE64_MARKER = ';base64,';
    let parts;
    let contentType;
    let raw;
    if (dataURL.indexOf(BASE64_MARKER) === -1) {
      parts = dataURL.split(',');
      contentType = parts[0].split(':')[1];
      raw = decodeURIComponent(parts[1]);
      return new Blob([raw], {
        type: contentType
      });
    }
    parts = dataURL.split(BASE64_MARKER);
    contentType = parts[0].split(':')[1];
    raw = window.atob(parts[1]);
    let rawLength = raw.length;
    let uInt8Array = new Uint8Array(rawLength);
    for (let i = 0; i < rawLength; ++i) {
      uInt8Array[i] = raw.charCodeAt(i);
    }
    return new Blob([uInt8Array], {
      type: contentType
    });
  }

  /**
   * file/blob 转 dataURL
   * @method fileOrBlobToDataURL
   * @for File/Logic
   * @param {object} file 需要转换的文件
   * @param {function} callback 回调方法
   */
  function fileOrBlobToDataURL(file, callback) {
    let F = new FileReader();
    F.readAsDataURL(file);
    F.onload = function (e) {
      callback(e.target.result);
    };
  }

  /**
   * dataURL 转 image
   * @method dataURLtoImage
   * @for File/compressAccurately
   * @param {string} dataURL 需要转换的内容
   * @param {function} callback 转换完成后的回调
   */
  function dataURLtoImage(dataURL, callback) {
    let img = new Image();

    img.onload = function () {
      callback(img);
    };

    img.src = dataURL;
  }

  /**
   * image 转 canvas
   * @method imagetoCanvas
   * @for File/compressAccurately
   * @param {string} image 需要转换的 image 对象
   * @param {object} config 需要转换的配置参数
   */
  function imagetoCanvas(image, config) {
    let cvs = document.createElement("canvas");
    let ctx = cvs.getContext('2d');

    cvs.width = config.width || image.width;
    cvs.height = config.height || image.height;
    ctx.drawImage(image, 0, 0, cvs.width, cvs.height);

    return cvs;
  }

  /**
   * canvas 转 file/blob
   * @method canvasResizetoFile
   * @for File/compressAccurately
   * @param {object} canvas canvas 对象
   * @param {number} quality 压缩比(0-1范围)
   * @param {string} type 图片格式
   * @param {function} callback 处理完成后的回调方法
   */
  function canvasResizetoFile(canvas, quality, type, callback) {
    canvas.toBlob(function (blob) {
      callback(blob);
    }, type, quality);
  }

  /**
   * 图片压缩
   * @method compressAccurately
   * @for File/Logic
   * @param {object} dataURL 需要压缩的 dataURL 文件
   * @param {object} config 压缩配置参数
   * @param {function} callback 压缩完成后的回调方法
   */
  function compressAccurately(dataURL, config, callback) {
    let blob = dataURLtoBlob(dataURL);

    // 如果指定体积不是数字或者数字字符串，则不做处理
    // 若是存在压缩格式转换，则处理图片格式
    config.size = +config.size;
    if (!config.size) {

      // 如果存在格式转换，则进行格式转换
      if (config.type) {
        formatConversion();
        return;
      } else {
        return callback(blob);
      }
    }

    // 如果指定体积大于原文件体积，则不做处理
    // 若是存在压缩格式转换，则处理图片格式
    if (config.size * 1024 > blob.size) {

      // 如果存在格式转换，则进行格式转换
      if (config.type) {
        formatConversion();
        return;
      } else {
        return callback(blob);
      }
    }

    // 通过格式大小判定后，进行后续图片转换格式初始化
    config.type = checkImageType(config.type) || 'image/jpeg';

    // 设置图片压缩精度
    config.accuracy = +config.accuracy;
    if (!config.accuracy || config.accuracy < 0.8 || config.accuracy > 0.99) {
      config.accuracy = 0.95;
    }

    // 设置压缩目标组
    let resultSize = {
      max: config.size * (2 - config.accuracy) * 1024,
      accurate: config.size * 1024,
      min: config.size * config.accuracy * 1024,
    };

    /**
     * 格式前置转换判断
     * @method formatConversion
     * @for compressAccurately
     */
    function formatConversion() {

      // 进行格式前置转换判断
      config.type = checkImageType(config.type) || 'image/jpeg';

      // 将图片转格式
      dataURLtoImage(dataURL, function (image) {
        let canvas = imagetoCanvas(image, Object.assign({}, config));
        canvasResizetoFile(canvas, 1, config.type, function (file) {
          callback(file);
        });
      });
    }

    // dataURL 转 image
    dataURLtoImage(dataURL, function (image) {
      let canvas = imagetoCanvas(image, Object.assign({}, config));
      let polling = 1;
      let imageQuality = 0.5;
      let compressFile = '';
      let tempDataURLs = [null, null];

      /**
       * 图片压缩轮询
       * HTMLCanvasElement.toBlob() 以及 HTMLCanvasElement.toDataURL() 压缩参数的最小细粒度为 0.01
       * 而 2 的 7 次方为 128，即只要循环 7 次，则会覆盖所有可能性
       * @method compress
       * @for compressAccurately
       */
      function compress() {
        canvasResizetoFile(canvas, imageQuality, config.type, function (file) {
          let CalculationSize = file.size;

          compressFile = file;

          // 如果到循环第七次还没有达到精确度的值，那说明该图片不能达到到此精确度要求
          // 这时候最后一次循环出来的 file 可能不是最精确的，需要取其周边两个 file 三者比较来选出最精确的；
          if (polling === 7) {
            if (resultSize.max < CalculationSize || resultSize.min > CalculationSize) {
              let accurate = '';
              let compressFileGroup = [compressFile, ...tempDataURLs];
              for (let i in compressFileGroup) {
                if (compressFileGroup[i]) {
                  if (typeof accurate === 'undefined' || accurate === null || accurate === '') {
                    let record = Math.abs(compressFileGroup[i].size - resultSize.accurate);
                    if (record < accurate) {
                      accurate = record;
                      compressFile = compressFileGroup[i];
                    }
                  } else {
                    accurate = Math.abs(compressFileGroup[i].size - resultSize.accurate);
                    compressFile = compressFileGroup[i];
                  }
                }
              }
            }
            end();
          } else if (resultSize.max < CalculationSize) {
            tempDataURLs[1] = compressFile;
            imageQuality -= Math.pow(0.5, (polling + 1));
            polling = polling + 1;
            compress();
          } else if (resultSize.min > CalculationSize) {
            tempDataURLs[0] = compressFile;
            imageQuality += Math.pow(0.5, (polling + 1));
            polling = polling + 1;
            compress();
          } else {
            end();
          }
        });
      }

      /**
       * 图片压缩结束方法
       * @method end
       * @for compress
       */
      function end() {
        if (compressFile.size > blob.size) {
          callback(blob);
        } else {
          callback(compressFile);
        }
      }

      // 进行图片压缩轮询
      compress();
    });
  }

  /**
   * 控制台错误/警告
   * @method throws
   * @for Logic
   * @param {number} error 抛出错误的类型 0:警告 1:错误
   * @param {number} num 输入警告文案编号
   * @param {string} dome 发生错误的节点
   */
  function throws(error, num, dome) {
    if (Kxui.configuration.debug) {
      let nums = {};
      nums[0] = '配置参数 {el} 不能为空';
      nums[1] = '无法找到 {' + dome + '} 节点或存在多个 {' + dome + '} 节点';
      nums[2] = '配置节点 {' + dome + '} 必须为 {input} 标签且属性必须为 {file} 上传模式';
      let text = 'Kxui-' + Kxui.about.version + '： 模块 {file} ' + nums[num] + '，请检查修改。';
      error === 1 ? console.error(text) : console.warn(text);
    }
  }

  /**
   * 文件处理解决方案
   * @method File
   */
  let File = function () {
    this.name = 'File';
    this.info = 'File Processing Solutions';
  };

  File.fn = File.prototype;

  /**
   * 文件解析
   * @method analysis
   * @for File
   * @param {object} parameter 配置参数
   */
  File.fn.analysis = function (parameter) {
    new Logic(parameter);
  };

  /**
   * dataURL 转 blob
   * @method dataURLtoBlob
   * @for File
   * @param {object} dataURL 需要转换的内容
   */
  File.fn.dataURLtoBlob = function (dataURL) {
    return dataURLtoBlob(dataURL);
  };

  /**
   * file/blob 转 dataURL
   * @method fileOrBlobToDataURL
   * @for File
   * @param {object} file 需要转换的文件
   * @param {function} callback 回调方法
   */
  File.fn.fileOrBlobToDataURL = function (file, callback) {
    fileOrBlobToDataURL(file, callback);
  };

  /**
   * dataURL 转 image
   * @method dataURLtoImage
   * @for File
   * @param {string} dataURL 需要转换的内容
   * @param {function} callback 转换完成后的回调
   */
  File.fn.dataURLtoImage = function (dataURL, callback) {
    dataURLtoImage(dataURL, callback);
  };


  /**
   * image 转 canvas
   * @method imagetoCanvas
   * @for File
   * @param {string} image 需要转换的 image 对象
   * @param {object} config 需要转换的配置参数(width:宽度;height:高度)
   */
  File.fn.imagetoCanvas = function (image, config) {
    return imagetoCanvas(image, config);
  };

  /**
   * canvas 转 file/blob
   * @method canvasResizetoFile
   * @for File
   * @param {object} canvas canvas 对象
   * @param {number} quality 压缩比(0-1范围)
   * @param {string} type 图片格式
   * @param {function} callback 处理完成后的回调方法
   */
  File.fn.canvasResizetoFile = function (canvas, quality, type, callback) {
    canvasResizetoFile(canvas, quality, type, callback);
  };

  /**
   * 图片压缩
   * @method compressAccurately
   * @for File
   * @param {object} dataURL 需要压缩的 dataURL 文件
   * @param {object} config 压缩配置参数
   * @param {function} callback 压缩完成后的回调方法
   */
  File.fn.compressAccurately = function (dataURL, config, callback) {
    compressAccurately(dataURL, config, callback);
  };

  return new File();

}));